import os
import threading
from flask import Flask, request, jsonify
import cv2
from pyzbar.pyzbar import decode
import requests
import redis
import json
from queue import Queue
import numpy as np
from dotenv import load_dotenv
from PIL import Image
import io
import hashlib
import re

# 加载.env文件中的配置数据
load_dotenv()
# APP启动端口
appPort = int(os.getenv('APP_PORT', 5000))
app = Flask(__name__)

# 读取是否启用Redis缓存的配置项
redisEnable = os.getenv('REDIS_ENABLE', 'False').lower()=='true'

# 根据配置决定是否连接Redis
if redisEnable:
    redisHost = os.getenv('REDIS_HOST', '127.0.0.1')
    redisPort = int(os.getenv('REDIS_PORT', 6379))
    redisAuth = os.getenv('REDIS_AUTH', '')
    redisDB = int(os.getenv('REDIS_DB', 0))
    if not redisAuth=='':
        Redis = redis.Redis(host=redisHost, port=redisPort, db=redisDB, password=redisAuth)
    else:
        Redis = redis.Redis(host=redisHost, port=redisPort, db=redisDB)
else:
    Redis = None


# 从GET/POST请求中获取links参数
def get_links():
    if request.method=='GET':
        links = request.args.get('links')
        # GET请求支持使用英文逗号隔开转成多个链接
        links = links.split(',')
    else:
        links = []
        if 'links' in request.json:
            links = request.json['links']
    return links


# md5
def md5(string):
    md5Hash = hashlib.md5()
    md5Hash.update(string.encode())
    return md5Hash.hexdigest()


## 验证是否链接地址
def is_valid_url(link):
    url_pattern = re.compile(r'^(http|https)://[a-zA-Z0-9\-.]+.[a-zA-Z]{2,}(?:/[a-zA-Z0-9\-._~:/?#[\]@!$&\'()*+,;=]*)?$')
    return bool(url_pattern.match(link))


# 二维码解析 支持GET/POST请求  请求参数为links
@app.route('/identify_qrcode', methods=['POST', 'GET'])
def identify_qrcode_endpoint():
    links = get_links()
    if len(links)==0:
        return jsonify({'error': 'No links provided'}), 400

    # 使用Queue来收集二维码数据
    qr_codes = Queue()

    # 创建一个空线程
    threads = []

    # 从队列中获取所有QR码并转换为字典形式
    qr_codes_dict = {}

    for link in links:
        if link:
            if not is_valid_url(link):
                qr_codes_dict[link] = formatResult({'error': '非链接地址'})
            else:
                # 加入到线程 参数1 目标方法 参数二 目标方法传参
                t = threading.Thread(target=process_image_from_link, args=(link, qr_codes))
                # 开始线程
                t.start()
                # 将线程加入空线程数组内
                threads.append(t)

    if len(threads)==0:
        return jsonify({'error': 'No links provided'}), 400
    # 循环调用所有线程执行且等待所有线程执行完毕
    for t in threads:
        t.join()

    # 从队列中获取到所有队列结果
    while not qr_codes.empty():
        result = qr_codes.get()
        qr_codes_dict[result['url']] = result['result']
    # 返回结果
    return jsonify(qr_codes_dict)


def is_gif(image_content):
    # 将np.uint8类型的文件流转换为bytes类型
    image_bytes = image_content.tobytes()

    # 创建BytesIO对象
    image_stream = io.BytesIO(image_bytes)

    # 读取文件流的前几个字节
    header = image_stream.read(3)

    # 判断文件头部是否为GIF格式的标识
    return header==b'GIF'


def gif_to_png_and_convert_to_np(image_content):
    # 将np.uint8类型的文件流转换为bytes类型
    image_bytes = image_content.tobytes()

    # 创建BytesIO对象
    image_stream = io.BytesIO(image_bytes)

    # 打开GIF文件
    gif_image = Image.open(image_stream)

    # 创建一个新的内存对象
    png_image_stream = io.BytesIO()

    # 保存为PNG格式
    gif_image.save(png_image_stream, format='PNG')

    # 将PNG格式的图片转换为np.uint8类型的文件流
    png_image_stream.seek(0)
    png_image_content = np.frombuffer(png_image_stream.read(), np.uint8)

    return png_image_content


def gif_to_png_and_convert_to_np(image_content):
    # 将np.uint8类型的文件流转换为bytes类型
    image_bytes = image_content.tobytes()

    # 创建BytesIO对象
    image_stream = io.BytesIO(image_bytes)

    # 打开GIF文件
    gif_image = Image.open(image_stream)

    # 创建一个新的内存对象
    png_image_stream = io.BytesIO()

    # 保存为PNG格式
    gif_image.save(png_image_stream, format='PNG')

    # 将PNG格式的图片转换为np.uint8类型的文件流
    png_image_stream.seek(0)
    png_image_content = np.frombuffer(png_image_stream.read(), np.uint8)

    return png_image_content


# 解析二维码 文件流 是否Git文件流
def identify_qrcode(fileStream, isGitStream=False):
    # 读取图片 并且使用彩色图片方式读取
    img = cv2.imdecode(fileStream, cv2.IMREAD_COLOR)
    if img is None:
        if not isGitStream and is_gif(fileStream):
            # 非GIF文件流 且本次文件流为GIF图片 则将GIT转为PNG图片
            gitFileStream = gif_to_png_and_convert_to_np(fileStream)
            # 重试
            return identify_qrcode(gitFileStream, True)
        return {'error': '图片无法解析', 'is_gif': is_gif(fileStream)}

    # 将图片转换成灰度颜色图片 用于简化图像处理分享复杂度
    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)

    # 解析标准二维码
    standard_barcodes = decode(gray)
    results = [d.data.decode() for d in standard_barcodes]
    mode = 'decode'
    if len(results)==0:
        # 初始化微信二维码检测器
        detector = cv2.wechat_qrcode.WeChatQRCode()
        barcodes, _ = detector.detectAndDecode(gray)
        # 合并结果
        results = barcodes
        mode = 'wechat'
    return {'result': results, 'count': len(results), 'mode': mode}


# 是否图片内容
def is_image_content(response):
    content_type = response.headers.get('Content-Type')
    if content_type is not None and 'image' in content_type:
        return True
    return False


# 格式化结果
def formatResult(result):
    result.setdefault('count', 0)
    result.setdefault('mode', None)
    result.setdefault('error', None)
    return result


# 获取缓存
def getCache(key):
    if redisEnable:
        return Redis.get(key)
    return None


# 设置缓存
def setCache(key, value, time=0):
    if redisEnable:
        if time==0:
            return Redis.set(key, value)
        return Redis.setex(key, 86400 * 30, value)
    return None


# 通过链接解析二维码图片进程
def process_image_from_link(link, qr_codes_queue):
    # 缓存KEY
    cacheKey = md5(link)
    # 先尝试从缓存中获取数据
    cached_result = getCache(cacheKey)
    if cached_result:
        # 存在缓存直接返回缓存结果
        result = json.loads(cached_result)
    else:
        try:
            # 没有缓存 直接请求链接转为文件流
            r = requests.get(link, stream=True, timeout=5)
            if r.status_code!=200:
                # 非法链接
                result = formatResult({'error': '解析异常'})
            elif not is_image_content(r):
                # 非图片链接
                result = formatResult({'error': '非图片'})
            else:
                # 转换为系统文件流
                fileStream = np.frombuffer(r.content, np.uint8)
                result = formatResult(identify_qrcode(fileStream))
                if result.get('error') is None:
                    # 没有异常时 将结果设置到缓存
                    setCache(cacheKey, json.dumps(result))
        except requests.Timeout:
            result = formatResult({'error': '请求超时'})
        except requests.RequestException as e:
            result = formatResult({'error': str(e)})

    # 将识别结果添加到线程安全队列
    qr_codes_queue.put({'url': link, 'result': result})


if __name__=='__main__':
    app.run(host='0.0.0.0', port=appPort)
